home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / group.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-23  |  66.3 KB  |  2,710 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     group.c
  4.  
  5.     This module handles group windows.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <ctype.h>
  13. #include <stdio.h>
  14.  
  15. #include "glob.h"
  16. #include "group.h"
  17. #include "wind.h"
  18. #include "newsrc.h"
  19. #include "newswatcher.h"
  20. #include "menus.h"
  21. #include "next.h"
  22. #include "mark.h"
  23. #include "newart.h"
  24. #include "subscribe.h"
  25. #include "search.h"
  26. #include "ldef.h"
  27. #include "olddrag.h"
  28. #include "dialog.h"
  29. #include "subject.h"
  30. #include "drawutil.h"
  31. #include "memutil.h"
  32. #include "listutil.h"
  33. #include "windutil.h"
  34. #include "tescroll.h"
  35. #include "strutil.h"
  36. #include "fileutil.h"
  37. #include "dragutil.h"
  38. #include "resutil.h"
  39. #include "key.h"
  40. #include "ic.h"
  41. #include "help.h"
  42.  
  43.  
  44.  
  45. #define kMaxFetchDialog        139
  46.  
  47. #define kFindDlg            157
  48. #define kFindPattern        4
  49. #define kStartAtBeginning    5
  50.  
  51. #define kMaxArticles        4
  52.  
  53. #define kMinWindowWidth        200            /* minimum window width */
  54.  
  55.  
  56.  
  57. static Boolean gFirstListClickCall;        /* true if first call to ClickLoop function */
  58. static OSErr gClickLoopErr;                /* click loop error code */
  59.  
  60. static WindowPtr gDragSrcWindow;        /* pointer to drag source window, or nil
  61.                                            is source was different application */
  62. static WindowPtr gDragDestWindow;        /* pointer to drag destination window */
  63. static Boolean gDragOptionKey;            /* true if option-drag */
  64. static short gDragDestRow;                /* current drag destination row */
  65. static short gFinalDragDestRow;            /* final drag destination row */
  66. static Handle gDragData;                /* handle to drag data */
  67.  
  68. static ListClickLoopUPP gGroupListClickLoopUPP;
  69. static ListClickLoopUPP gOldListClickLoopUPP;
  70. static DragTrackingHandlerUPP gHandleTrackingUPP;
  71. static DragReceiveHandlerUPP gHandleReceiveUPP;
  72.  
  73.  
  74.  
  75. /*----------------------------------------------------------------------------
  76.     DoFindDialog
  77.     
  78.     Present the find dialog.
  79.             
  80.     Exit:    function result = error code.
  81.             gFindPattern = search string.
  82. ----------------------------------------------------------------------------*/
  83.  
  84. OSErr DoFindDialog (void)
  85. {
  86.     CStr255 pattern;
  87.     DialogPtr dlg = nil;
  88.     short item;
  89.     OSErr err = noErr;
  90.     EventRecord ev;
  91.     
  92.     err = MyGetNewDialog(kFindDlg, ok, cancel, &dlg);
  93.     if (err != noErr) return err;
  94.     RestoreMovableModalDialogPosition(dlg, gPrefs.findLoc);
  95.     strcpy(pattern, gFindPattern);
  96.  
  97.     DlgSetCString(dlg, kFindPattern, pattern);
  98.     SetItemMaxLength(dlg, kFindPattern, 255);
  99.     DlgSetCheck(dlg, kStartAtBeginning, gPrefs.startFindAtBeginning);
  100.     SelectDialogItemText(dlg, kFindPattern, 0, 255);
  101.     
  102.     do {
  103.         DlgEnableItem(dlg, ok, *pattern != 0);
  104.         MyMovableModalDialog(dlg, DialogFilter, &item);
  105.         switch (item) {
  106.             case kFindPattern:
  107.                 DlgGetCString(dlg, kFindPattern, pattern);
  108.                 break;
  109.             case kStartAtBeginning:
  110.                 DlgToggleCheck(dlg, kStartAtBeginning);
  111.                 break;
  112.         }
  113.     } while (item != ok && item != cancel);
  114.     
  115.     if (item == ok) {
  116.         strcpy(gFindPattern, pattern);
  117.         gPrefs.startFindAtBeginning = DlgGetCheck(dlg, kStartAtBeginning);
  118.     }
  119.     
  120.     SaveMovableModalDialogPosition(dlg, &gPrefs.findLoc);
  121.     err = DoClose(dlg);
  122.     if (err != noErr) return err;
  123.     
  124.     while (WaitNextEvent(activMask | updateMask | osMask, &ev, 0, nil)) 
  125.         HandleEvent(&ev);
  126.     
  127.     if (item == cancel) return userCanceledErr;
  128.     
  129.     return noErr;
  130. }
  131.  
  132.  
  133.  
  134. /*----------------------------------------------------------------------------
  135.     BuildGroupScrapData 
  136.     
  137.     Build scrap data for all the selected groups in a group list window.
  138.             
  139.     Entry:    wind = pointer to group list window.
  140.     
  141.     Exit:    function result = error code.
  142.             *privateScrap = handle to private type 'NNTP' scrap data for selected groups.
  143.             *publicScrap = handle to public type 'TEXT' scrap data for selected groups.
  144.             
  145.     The scrap data is used for group cut/copy/paste, and as the flavor
  146.     data for group drags.
  147.     
  148.     The format of the private 'NNTP' scrap data is:
  149.     
  150.     Scrap subtype (long): 'GRUP'
  151.     Version number of NewsWatcher which created the scrap data (unsigned long).
  152.     Minimum version number of NewsWatcher which can read this scrap data
  153.         (unsigned long).
  154.     Number of groups in list (long).
  155.     
  156.     For each group in the list:
  157.     
  158.     P-format group name string, rounded up to longword boundary.
  159.     Low article number (long).
  160.     High article number (long).
  161.     Number of unread articles (long).
  162.     Number of items in list of unread articles (long).
  163.     List of unread article ranges (two longs each).
  164.     
  165.     As a special case, for groups copied or dragged from the full group list 
  166.     or from the new groups list:
  167.     
  168.         low article number = -1
  169.         high article number = 0
  170.         number of unread articles = 0
  171.         number of items in list of unread articles = 0
  172.     
  173.     When such a group is later inserted into a user group list, we must 
  174.     query the server to subscribe to the group. 
  175.         
  176.     The public 'TEXT' scrap data is a list of the group names separated by
  177.     commas.
  178. ----------------------------------------------------------------------------*/
  179.  
  180. static OSErr BuildGroupScrapData (WindowPtr wind, Handle *privateScrap, 
  181.     Handle *publicScrap)
  182. {
  183.     TWindow **info;
  184.     TGroup **groupArray, theGroup;
  185.     ListHandle theList;
  186.     Handle pri = nil;
  187.     long priAllocated, priNext;
  188.     Handle pub = nil;
  189.     long pubAllocated, pubNext;
  190.     Cell theCell;
  191.     short cellDataLen, index;
  192.     CStr255 groupName;
  193.     OSErr err = noErr;
  194.     unsigned long versNumber;
  195.     Boolean userGroupList;
  196.     long numGroups;
  197.     long groupNameLen;
  198.     long bytesNeeded;
  199.     TUnread **unread;
  200.     long numUnreadPairs;
  201.     
  202.     info = (TWindow**)GetWRefCon(wind);
  203.     groupArray = (**info).groupArray;
  204.     theList = (**info).theList;
  205.     userGroupList = (**info).groupKind == kUserGroup;
  206.     numGroups = NumListItemsSelected(theList);
  207.     
  208.     if (userGroupList) {
  209.         err = UpdateAllUnreadLists(wind);
  210.         if (err != noErr) goto exit;
  211.     }
  212.     
  213.     err = MyNewHandle(1000, &pri);
  214.     if (err != noErr) goto exit;
  215.     priAllocated = 1000;
  216.     priNext = 0;
  217.     
  218.     err = MyNewHandle(1000, &pub);
  219.     if (err != noErr) goto exit;
  220.     pubAllocated = 1000;
  221.     pubNext = 0;
  222.     
  223.     *(OSType*)(*pri + priNext) = 'GRUP';
  224.     priNext += sizeof(OSType);
  225.     
  226.     err = GetVersionNumber(&versNumber);
  227.     if (err != noErr) goto exit;
  228.     *(unsigned long*)(*pri + priNext) = versNumber;
  229.     priNext += sizeof(unsigned long);
  230.     
  231.     *(unsigned long*)(*pri + priNext) = 0x02002043; /* 2.0d43 */
  232.     priNext += sizeof(unsigned long);
  233.     
  234.     *(long*)(*pri + priNext) = numGroups;
  235.     priNext += sizeof(long);
  236.  
  237.     SetPt(&theCell, 0, 0);
  238.     
  239.     while (LGetSelect(true, &theCell, theList)) {
  240.     
  241.         cellDataLen = 2;
  242.         LGetCell(&index, &cellDataLen, theCell, theList);
  243.         theGroup = (*groupArray)[index];
  244.         strcpy(groupName, *gGroupNames + theGroup.nameOffset);
  245.         groupNameLen = strlen(groupName);
  246.         
  247.         bytesNeeded = groupNameLen;
  248.         if (pubNext != 0) bytesNeeded += 2;
  249.         if (pubNext + bytesNeeded > pubAllocated) {
  250.             pubAllocated += 1000;
  251.             err = MySetHandleSize(pub, pubAllocated);
  252.             if (err != noErr) goto exit;
  253.         }
  254.         if (pubNext != 0) {
  255.             BlockMoveData(", ", *pub + pubNext, 2);
  256.             pubNext += 2;
  257.         }
  258.         BlockMoveData(groupName, *pub + pubNext, groupNameLen);
  259.         pubNext += groupNameLen;
  260.         
  261.         bytesNeeded = groupNameLen + 1;
  262.         bytesNeeded = ((bytesNeeded + 3) >> 2) << 2;
  263.         bytesNeeded += 4*sizeof(long);
  264.         if (userGroupList) {
  265.             numUnreadPairs = 0;
  266.             for (unread = theGroup.unread; unread != nil; unread = (**unread).next) {
  267.                 bytesNeeded += 2*sizeof(long);
  268.                 numUnreadPairs++;
  269.             }
  270.         }
  271.         if (priNext + bytesNeeded > priAllocated) {
  272.             priAllocated += bytesNeeded > 1000 ? bytesNeeded : 1000;
  273.             err = MySetHandleSize(pri, priAllocated);
  274.             if (err != noErr) goto exit;
  275.         }
  276.         *(*pri + priNext) = groupNameLen;
  277.         priNext++;
  278.         BlockMoveData(groupName, *pri + priNext, groupNameLen);
  279.         priNext += groupNameLen;
  280.         priNext = ((priNext + 3) >> 2) << 2;
  281.         if (userGroupList) {
  282.             *(long*)(*pri + priNext) = theGroup.firstMess;
  283.             priNext += 4;
  284.             *(long*)(*pri + priNext) = theGroup.lastMess;
  285.             priNext += 4;
  286.             *(long*)(*pri + priNext) = theGroup.numUnread;
  287.             priNext += 4;
  288.             *(long*)(*pri + priNext) = numUnreadPairs;
  289.             priNext += 4;
  290.             for (unread = theGroup.unread; unread != nil; unread = (**unread).next) {
  291.                 *(long*)(*pri + priNext) = (**unread).firstUnread;
  292.                 priNext += 4;
  293.                 *(long*)(*pri + priNext) = (**unread).lastUnread;
  294.                 priNext += 4;
  295.             }
  296.         } else {
  297.             *(long*)(*pri + priNext) = -1;
  298.             priNext += 4;
  299.             *(long*)(*pri + priNext) = 0;
  300.             priNext += 4;
  301.             *(long*)(*pri + priNext) = 0;
  302.             priNext += 4;
  303.             *(long*)(*pri + priNext) = 0;
  304.             priNext += 4;
  305.         }
  306.         
  307.         theCell.v++;
  308.         
  309.     }
  310.     
  311.     MySetHandleSize(pri, priNext);
  312.     MySetHandleSize(pub, pubNext);
  313.     *privateScrap = pri;
  314.     *publicScrap = pub;
  315.     return noErr;
  316.     
  317. exit:
  318.  
  319.     MyDisposeHandle(pri);
  320.     MyDisposeHandle(pub);
  321.     return err;
  322. }
  323.  
  324.  
  325.  
  326. /*----------------------------------------------------------------------------
  327.     CheckScrapData 
  328.     
  329.     Check scrap data to make sure it is valid. Issue an error message if
  330.     the data is invalid or cannot be read because this version of NewsWatcher
  331.     is too old to understand the data format.
  332.             
  333.     Entry:    data = handle to scrap data.
  334.     
  335.     Exit:    function result = error code = noErr if valid, userCanceledErr
  336.                 if not valid.
  337. ----------------------------------------------------------------------------*/
  338.  
  339. OSErr CheckScrapData (Handle data)
  340. {
  341.     char state;
  342.     Ptr p, pEnd;
  343.     OSType subType;
  344.     unsigned long minVersion, myVersion;
  345.     long numGroups;
  346.     unsigned char groupNameLen;
  347.     long firstMess, lastMess, numUnread;
  348.     long numUnreadPairs, prevLastUnread, firstUnread, lastUnread;
  349.     OSErr err = noErr;
  350.  
  351.     state = MyHGetState(data);
  352.     MyHLock(data);
  353.     
  354.     p = *data;
  355.     pEnd = *data + MyGetHandleSize(data);
  356.     
  357.     subType = *(OSType*)p;
  358.     p += sizeof(OSType);
  359.     if (p > pEnd || subType != 'GRUP') goto exit;
  360.     
  361.     p += sizeof(unsigned long);
  362.     if (p > pEnd) goto exit;
  363.     
  364.     minVersion = *(unsigned long*)p;
  365.     p += sizeof(unsigned long);
  366.     if (p > pEnd) goto exit;
  367.     err = GetVersionNumber(&myVersion);
  368.     if (err != noErr) goto exit;
  369.     if (myVersion < minVersion) goto exit;
  370.     
  371.     numGroups = *(long*)p;
  372.     p += sizeof(long);
  373.     if (p > pEnd) goto exit;
  374.     if (numGroups <= 0 || numGroups > 0x7fff) goto exit;
  375.     
  376.     while (numGroups--) {
  377.     
  378.         groupNameLen = *(unsigned char*)p;
  379.         if (groupNameLen == 0) goto exit;
  380.         groupNameLen++;
  381.         groupNameLen = ((groupNameLen + 3) >> 2) << 2;
  382.         p += groupNameLen;
  383.         if (p > pEnd) goto exit;
  384.         
  385.         firstMess = *(long*)p;
  386.         p += sizeof(long);
  387.         if (p > pEnd || firstMess < -1) goto exit;
  388.         
  389.         lastMess = *(long*)p;
  390.         p += sizeof(long);
  391.         if (p > pEnd || lastMess < 0) goto exit;
  392.         
  393.         numUnread = *(long*)p;
  394.         p += sizeof(long);
  395.         if (p > pEnd || numUnread < 0) goto exit;
  396.         
  397.         numUnreadPairs = *(long*)p;
  398.         p += sizeof(long);
  399.         if (p > pEnd || numUnreadPairs < 0) goto exit;
  400.         
  401.         prevLastUnread = -1;
  402.         while (numUnreadPairs--) {
  403.             firstUnread = *(long*)p;
  404.             p += sizeof(long);
  405.             if (p > pEnd || firstUnread < 0) goto exit;
  406.             lastUnread = *(long*)p;
  407.             p += sizeof(long);
  408.             if (p > pEnd || lastUnread < 0) goto exit;
  409.             if (firstUnread > lastUnread) goto exit;
  410.             if (firstUnread <= prevLastUnread) goto exit;
  411.             prevLastUnread = lastUnread;
  412.         }
  413.     }
  414.     
  415.     if (p != pEnd) goto exit;    
  416.         
  417.     MyHSetState(data, state);
  418.     return noErr;
  419.  
  420. exit:
  421.  
  422.     MyHSetState(data, state);
  423.     ErrorMessageNumber(kStrBadScrapData);
  424.     return userCanceledErr;
  425. }
  426.  
  427.  
  428.  
  429. /*----------------------------------------------------------------------------
  430.     DisposeGroupUnreadList 
  431.     
  432.     Dispose the unread list for a group.
  433.             
  434.     Entry:    theGroup = pointer to group record.
  435. ----------------------------------------------------------------------------*/
  436.  
  437. void DisposeGroupUnreadList (TGroup *theGroup)
  438. {
  439.     TUnread **pUnreadRec, **qUnreadRec;
  440.  
  441.     pUnreadRec = theGroup->unread;
  442.     while (pUnreadRec != nil) {
  443.         qUnreadRec = (**pUnreadRec).next;
  444.         MyDisposeHandle(pUnreadRec);
  445.         pUnreadRec = qUnreadRec;
  446.     }
  447.     theGroup->unread = nil;
  448.     theGroup->numUnread = 0;
  449. }
  450.  
  451.  
  452.  
  453. /*----------------------------------------------------------------------------
  454.     DisposeGroupArray 
  455.     
  456.     Dispose a group array.
  457.             
  458.     Entry:    groupArray = handle to group array.
  459.             numGroups = number of groups in array.
  460. ----------------------------------------------------------------------------*/
  461.  
  462. void DisposeGroupArray (TGroup **groupArray, short numGroups)
  463. {
  464.     TGroup *p, *pEnd;
  465.     
  466.     if (groupArray == nil) return;
  467.     MyHLock(groupArray);
  468.     for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
  469.         DisposeGroupUnreadList(p);
  470.     MyDisposeHandle(groupArray);
  471. }
  472.  
  473.  
  474.  
  475. /*----------------------------------------------------------------------------
  476.     FixHeight 
  477.     
  478.     Round down window height to an exact multiple of lines.
  479.             
  480.     Entry:    wind = pointer to group window.
  481.             *height = window height.
  482.             
  483.     Exit:    *height = adjusted window height
  484. ----------------------------------------------------------------------------*/
  485.  
  486. static void FixHeight (WindowPtr wind, short *height)
  487. {
  488.     TWindow **info;
  489.     short panelHeight, lineHeight, adjust;
  490.  
  491.     info = (TWindow**)GetWRefCon(wind);
  492.     panelHeight = (**info).panelHeight;
  493.     lineHeight = GetFontLineHeight(wind);
  494.     adjust = panelHeight + 15;
  495.     *height = (*height - adjust) / lineHeight * lineHeight + adjust;
  496. }
  497.  
  498.  
  499.  
  500. /*----------------------------------------------------------------------------
  501.     MinHeight 
  502.     
  503.     Compute the minimum height of a group window.
  504.             
  505.     Entry:    wind = pointer to group window.
  506. ----------------------------------------------------------------------------*/
  507.  
  508. static short MinHeight (WindowPtr wind)
  509. {
  510.     TWindow **info;
  511.     short lineHeight, height, extra;
  512.     
  513.     info = (TWindow**)GetWRefCon(wind);
  514.     lineHeight = GetFontLineHeight(wind);
  515.     extra = lineHeight + 15;
  516.     if (extra < 65) extra = 65 + lineHeight;
  517.     height = (**info).panelHeight + extra;
  518.     FixHeight(wind, &height);
  519.     return height;
  520. }
  521.  
  522.  
  523.  
  524. /*----------------------------------------------------------------------------
  525.     Scroll 
  526.     
  527.     Scroll a group window.
  528.             
  529.     Entry:    wind = pointer to group window.
  530.             part = part code.
  531. ----------------------------------------------------------------------------*/
  532.  
  533. static void Scroll (WindowPtr wind, short part)
  534. {
  535.     TWindow **info;
  536.     ListHandle theList;
  537.     short height, numCells;
  538.     Cell theCell;
  539.     
  540.     info = (TWindow**)GetWRefCon(wind);
  541.     theList = (**info).theList;
  542.     height = (**theList).visible.bottom - (**theList).visible.top;
  543.     numCells = (**theList).dataBounds.bottom;
  544.     switch (part) {
  545.         case inUpButton:
  546.         case inDownButton:
  547.             SetPt(&theCell, 0, 0);
  548.             if (part == inUpButton) {
  549.                 theCell.v = GetFirstSelectedCell(theList);
  550.                 theCell.v--;
  551.                 if (theCell.v < 0) theCell.v = 0;
  552.             } else { 
  553.                 theCell.v = GetLastSelectedCell(theList);
  554.                 theCell.v++;
  555.                 if (theCell.v >= numCells) theCell.v = numCells-1;
  556.             }
  557.             SelectSingleListItem(theList, theCell);
  558.             HandleUpdate(wind);
  559.             MyLAutoScroll(theList);
  560.             break;
  561.         case inPageUp:
  562.             MyLScroll(-(height - 1), theList);
  563.             break;
  564.         case inPageDown:
  565.             MyLScroll(height - 1, theList);
  566.             break;
  567.         case kScrollToHome:
  568.             MyLScroll(-numCells, theList);
  569.             break;
  570.         case kScrollToEnd:
  571.             MyLScroll(numCells, theList);
  572.             break;
  573.     }
  574. }
  575.  
  576.  
  577.  
  578. /*----------------------------------------------------------------------------
  579.     ResizeContents 
  580.     
  581.     Adjust a group window's contents after a window size change (grow
  582.     or zoom).
  583.             
  584.     Entry:    wind = pointer to group window.
  585. ----------------------------------------------------------------------------*/
  586.  
  587. static void ResizeContents (WindowPtr wind)
  588. {
  589.     TWindow **info;
  590.     short width, height, panelHeight;
  591.     ListHandle theList;
  592.     Point x;
  593.  
  594.     info = (TWindow**)GetWRefCon(wind);
  595.     panelHeight = (**info).panelHeight;
  596.     theList = (**info).theList;
  597.     width = wind->portRect.right;
  598.     height = wind->portRect.bottom;
  599.  
  600.     LSize(width-15, height-15-panelHeight, theList);
  601.     SetPt(&x, width-15, (**theList).cellSize.v);
  602.     LCellSize(x, theList);
  603.     
  604.     InvalRect(&wind->portRect);
  605. }
  606.  
  607.  
  608.  
  609. /*----------------------------------------------------------------------------
  610.     GroupNameMatchesPattern
  611.     
  612.     Compare a find pattern string to a group name.
  613.             
  614.     Entry:    pattern = pointer to find pattern string.
  615.             groupName = pointer to group name.
  616.             patternContainsPeriod = true if pattern string contains a 
  617.                 period.
  618.             
  619.     Exit:    function result = true if group name matches pattern
  620. ----------------------------------------------------------------------------*/
  621.  
  622. static Boolean GroupNameMatchesPattern (char *pattern, char *groupName,
  623.     Boolean patternContainsPeriod)
  624. {
  625.     char *pat, *grp;
  626.  
  627.     if (patternContainsPeriod) {
  628.         pat = pattern;
  629.         grp = groupName;
  630.         while (*pat != 0) {
  631.             if (*pat == '.') {
  632.                 while (*grp != 0 && *grp != '.') grp++;
  633.                 if (*grp == 0) {
  634.                     goto exit;
  635.                 } else {
  636.                     grp++;
  637.                 }
  638.             } else {
  639.                 if (toupper(*pat) != toupper(*grp)) goto exit;
  640.                 grp++;
  641.             }
  642.             pat++;
  643.         }
  644.         return true;
  645.     }
  646.     
  647. exit:
  648.  
  649.     return MyIsASubstring(groupName, pattern);
  650. }
  651.  
  652.  
  653.  
  654. /*----------------------------------------------------------------------------
  655.     Find 
  656.     
  657.     Search a group window for a pattern.
  658.             
  659.     Entry:    wind = pointer to group window.
  660.             theCell = first cell to search.
  661.             gFindPattern = pattern.
  662.     
  663.     Exit:    function result = error code.
  664. ----------------------------------------------------------------------------*/
  665.  
  666. static OSErr Find (WindowPtr wind, Cell theCell)
  667. {
  668.     TWindow **info;
  669.     TGroup **groupArray;
  670.     ListHandle theList;
  671.     short index, cellDataLen, numCells;
  672.     char *groupName;
  673.     Boolean patternContainsPeriod = false;
  674.     char *p;
  675.     unsigned long tickLongTime;
  676.     Boolean longTime = false;
  677.     OSErr err = noErr;
  678.     char state;
  679.     
  680.     info = (TWindow**)GetWRefCon(wind);
  681.     groupArray = (**info).groupArray;
  682.     theList = (**info).theList;
  683.     numCells = (**theList).dataBounds.bottom;
  684.     for (p = gFindPattern; *p != 0; p++) {
  685.         if (*p == '.') {
  686.             patternContainsPeriod = true;
  687.             break;
  688.         }
  689.     }
  690.     tickLongTime = TickCount() + 30;
  691.     state = MyHGetState(gGroupNames);
  692.     MyHLock(gGroupNames);
  693.     while (theCell.v < numCells) {
  694.         cellDataLen = 2;
  695.         LGetCell(&index, &cellDataLen, theCell, theList);
  696.         groupName = *gGroupNames + (*groupArray)[index].nameOffset;
  697.         if (GroupNameMatchesPattern(gFindPattern, groupName, patternContainsPeriod)) {
  698.             MyHSetState(gGroupNames, state);
  699.             SelectSingleListItem(theList, theCell);
  700.             MyLScrollCenter(theCell, theList);
  701.             return noErr;
  702.         }
  703.         theCell.v++;
  704.         if ((theCell.v & 0xf) == 0) {
  705.             if (!longTime && TickCount() > tickLongTime) longTime = true;
  706.             if (longTime) {
  707.                 err = GiveTime(false);
  708.                 if (err != noErr) {
  709.                     MyHSetState(gGroupNames, state);
  710.                     return err;
  711.                 }
  712.             }
  713.         }
  714.     }
  715.     MyHSetState(gGroupNames, state);
  716.     SysBeep(0);
  717.     return noErr;
  718. }
  719.  
  720.  
  721.  
  722. /*----------------------------------------------------------------------------
  723.     MakeGroupList 
  724.     
  725.     Creates a List Manager list for a group window.
  726.  
  727.     Entry:    numGroups = number of groups in list.
  728.             theList = handle to list record.
  729.  
  730.     Exit:    function result = error code.
  731.             Any previous list is deleted. A new list is created with numGroups
  732.             cells. The cell data is initialized to 0, 1, 2, ..., numGroups-1.
  733. ----------------------------------------------------------------------------*/
  734.  
  735. OSErr MakeGroupList (short numGroups, ListHandle theList)
  736. {
  737.     short i;
  738.     short *pCells;
  739.     short *pCellArray;
  740.     short offset;
  741.     OSErr err = noErr;
  742.     
  743.     if (!MemoryAvailable(4*numGroups)) return memFullErr;
  744.  
  745.     LSetDrawingMode(false, theList);
  746.     LDelRow(0, 0, theList);
  747.     LAddRow(numGroups, 0, theList);
  748.     
  749.     err = MySetHandleSize((**theList).cells, 2*numGroups);
  750.     if (err != noErr) goto exit;
  751.  
  752.     pCells = (short*)*((**theList).cells);
  753.     pCellArray = (**theList).cellArray;
  754.     offset = 0;
  755.  
  756.     for (i=0; i<numGroups; i++) {
  757.         *pCellArray++ = offset;
  758.         *pCells++ = i;
  759.         offset += 2;
  760.     }
  761.     *pCellArray = offset;
  762.     LSetDrawingMode(true, theList);
  763.     return noErr;
  764.     
  765. exit:
  766.  
  767.     LDelRow(0, 0, theList);
  768.     LSetDrawingMode(true, theList);
  769.     return err;
  770. }
  771.  
  772.  
  773.  
  774. /*----------------------------------------------------------------------------
  775.     CanAcceptDrag 
  776.     
  777.     Figure out whether a dragged object can be accepted by a user group
  778.     list window.
  779.     
  780.     Entry:    theDrag = drag reference
  781.             
  782.     Exit:    function result = true if this object can be accepted by a user
  783.                 group list window.
  784. ----------------------------------------------------------------------------*/
  785.  
  786. static Boolean CanAcceptDrag (DragReference theDrag)
  787. {
  788.     unsigned short numItems;
  789.     ItemReference theItem;
  790.     FlavorFlags theFlags;
  791.     OSErr err = noErr;
  792.     short i;
  793.     
  794.     CountDragItems(theDrag, &numItems);
  795.     for (i = 1; i <= numItems; i++) {
  796.         GetDragItemReferenceNumber(theDrag, i, &theItem);
  797.         err = GetFlavorFlags(theDrag, theItem, kNewsWatcherSignature, &theFlags);
  798.         if (err != noErr) return false;
  799.     }
  800.     return true;
  801. }
  802.  
  803.  
  804.  
  805. /*----------------------------------------------------------------------------
  806.     HandleTracking 
  807.     
  808.     Drag Manager tracking handler for user group list windows.
  809.     
  810.     Entry:    message = tracking message from Drag Manager.
  811.             wind = pointer to user group list window.
  812.             handlerRefCon = reference constant (nil).
  813.             theDrag = drag reference.
  814.             
  815.     Exit:    function result = error code.
  816. ----------------------------------------------------------------------------*/
  817.  
  818. static pascal OSErr HandleTracking (DragTrackingMessage message,
  819.     WindowPtr wind, void *handlerRefCon, DragReference theDrag)
  820. {
  821.     TWindow **info;
  822.     ListHandle theList;
  823.     Rect rView, contentRect, visible, dataBounds;
  824.     Point where;
  825.     DragAttributes attributes;
  826.     static Boolean canAcceptDrag;
  827.     static Boolean hilite;
  828.     short newDestRow;
  829.     RgnHandle rgn = nil;
  830.     Boolean inContentRect, leftSender, inSender, canAutoScroll;
  831.     short scrollDelta;
  832.     static short prevScrollDelta;
  833.     static long scrollTickCount;
  834.     
  835.     if (gLongOperation || gInDialog) return userCanceledErr;
  836.     
  837.     if (gDragErr != noErr) message = dragTrackingLeaveWindow;
  838.     
  839.     GetDragAttributes(theDrag, &attributes);
  840.     leftSender = (attributes & dragHasLeftSenderWindow) != 0;
  841.     inSender = (attributes & dragInsideSenderWindow) != 0;
  842.     info = (TWindow**)GetWRefCon(wind);
  843.     theList = (**info).theList;
  844.     visible = (**theList).visible;
  845.     dataBounds = (**theList).dataBounds;
  846.     rView = (**theList).rView;
  847.     contentRect = rView;
  848.     contentRect.top = 0;
  849.     contentRect.bottom = wind->portRect.bottom;
  850.     
  851.     /* Note: Apple's "Drag and Drop Human Interface Guidelines" prohibit autoscrolling
  852.        unless the source and destination windows are the same and the window is
  853.        frontmost. I do not like this rule, and have deliberately broken it. I permit
  854.        autoscrolling inactive windows during group drags. */
  855.     
  856. /*    canAutoScroll = inSender && wind == FrontWindow();*/
  857.     canAutoScroll = true;
  858.     
  859.     switch (message) {
  860.     
  861.         case dragTrackingEnterHandler:
  862.         
  863.             break;
  864.             
  865.         case dragTrackingEnterWindow:
  866.         
  867.             canAcceptDrag = CanAcceptDrag(theDrag);
  868.             hilite = false;
  869.             gDragDestRow = -1;
  870.             prevScrollDelta = 0;
  871.             break;
  872.             
  873.         case dragTrackingInWindow:
  874.         
  875.             if (!canAcceptDrag) break;
  876.             GetDragMouse(theDrag, &where, nil);
  877.             GlobalToLocal(&where);
  878.             inContentRect = PtInRect(where, &contentRect);
  879.             if (inContentRect) {
  880.                 if (leftSender && !hilite) {
  881.                     rgn = NewRgn();
  882.                     RectRgn(rgn, &rView);
  883.                     ShowDragHilite(theDrag, rgn, true);
  884.                     DisposeRgn(rgn);
  885.                     hilite = true;
  886.                 }
  887.                 scrollDelta = 0;
  888.                 if (canAutoScroll && gDragDestRow >= 0) {
  889.                     if (where.v < rView.top && visible.top > 0) {
  890.                         scrollDelta = -1;
  891.                     } else if (where.v > rView.bottom && 
  892.                         visible.bottom < dataBounds.bottom) 
  893.                     {
  894.                         scrollDelta = +1;
  895.                     }
  896.                 }
  897.                 if (scrollDelta == prevScrollDelta) {
  898.                     if (scrollDelta != 0 && TickCount() - scrollTickCount < 10) 
  899.                         scrollDelta = 0;
  900.                 } else {
  901.                     prevScrollDelta = scrollDelta;
  902.                     scrollDelta = 0;
  903.                     scrollTickCount = TickCount();
  904.                 } 
  905.                 if (scrollDelta != 0) {
  906.                     DrawListDividingLine(theList, gDragDestRow);
  907.                     gDragDestRow = -1;
  908.                     gDragErr = DragPreScroll(theDrag, 0, 
  909.                         -scrollDelta * (**theList).cellSize.v);
  910.                     if (gDragErr != noErr) return gDragErr;
  911.                     MyLScroll(scrollDelta, theList);
  912.                     gDragErr = DragPostScroll(theDrag);
  913.                     if (gDragErr != noErr) return gDragErr;
  914.                 }
  915.                 newDestRow = ListDestinationRow(theList, where);
  916.                 if (newDestRow != gDragDestRow) {
  917.                     if (gDragDestRow >= 0) DrawListDividingLine(theList, gDragDestRow);
  918.                     gDragDestRow = newDestRow;
  919.                     DrawListDividingLine(theList, gDragDestRow);
  920.                 }
  921.             } else {
  922.                 if (gDragDestRow >= 0) {
  923.                     DrawListDividingLine(theList, gDragDestRow);
  924.                     gDragDestRow = -1;
  925.                 }
  926.                 if (hilite) {
  927.                     HideDragHilite(theDrag);
  928.                     hilite = false;
  929.                 }
  930.                 prevScrollDelta = 0;
  931.             }
  932.             break;
  933.             
  934.         case dragTrackingLeaveWindow:
  935.         
  936.             if (gDragDestRow >= 0) DrawListDividingLine(theList, gDragDestRow);
  937.             gDragDestRow = -1;
  938.             if (hilite) HideDragHilite(theDrag);
  939.             hilite = false;
  940.             prevScrollDelta = 0;
  941.             break;
  942.     
  943.         case dragTrackingLeaveHandler:
  944.         
  945.             break;
  946.             
  947.     }
  948.  
  949.     return noErr;
  950. }
  951.  
  952.  
  953.  
  954. /*----------------------------------------------------------------------------
  955.     HandleReceivePostProcessor 
  956.     
  957.     Drag Manager receive handler post processor for user group list windows.
  958.     
  959.     Entry:    gDragSrcWindow = pointer to drag source window, or nil if
  960.                 source was different application.
  961.             gDragDestWindow = pointer to destination window.
  962.             gDragOptionKey = true if option-drag.
  963.             gFinalDragDestRow = final drag destination row.
  964.             gDragData = handle to drag data if source was different application.
  965.             
  966.     Exit:    function result = error code.
  967.     
  968.     Note: User interaction and network transactions are permitted in
  969.     this function.
  970. ----------------------------------------------------------------------------*/
  971.  
  972. static OSErr HandleReceivePostProcessor (void)
  973. {
  974.     TWindow **srcInfo;
  975.     TGroupWindowKind srcKind;
  976.     Boolean changed;
  977.     OSErr err = noErr;
  978.     
  979.     if (gDragSrcWindow == nil) {
  980.         err = CopyGroupsFromScrap(gDragData, gDragDestWindow, gFinalDragDestRow);
  981.         MyDisposeHandle(gDragData);
  982.         return err;
  983.     }
  984.  
  985.     srcInfo = (TWindow**)GetWRefCon(gDragSrcWindow);
  986.     srcKind = (**srcInfo).groupKind;
  987.     
  988.     switch (srcKind) {
  989.     
  990.         case kUserGroup:
  991.         
  992.             if (gDragDestWindow == gDragSrcWindow) {
  993.                 MoveSelectedListCells((**srcInfo).theList, gFinalDragDestRow, &changed);
  994.                 if (changed) (**srcInfo).changed = true;
  995.             } else {
  996.                 err = CopyOrMoveSelectedGroups(gDragSrcWindow, gDragDestWindow, 
  997.                     gFinalDragDestRow, gDragOptionKey);
  998.                 if (err != noErr) return err;
  999.             }
  1000.             break;
  1001.             
  1002.         case kFullGroup:
  1003.         case kNewGroup:
  1004.         
  1005.             err = CopyOrMoveSelectedGroups(gDragSrcWindow, gDragDestWindow, 
  1006.                 gFinalDragDestRow, true);
  1007.             if (err != noErr) return err;
  1008.             break;
  1009.     }
  1010.     
  1011.     return noErr;
  1012. }
  1013.  
  1014.  
  1015.  
  1016. /*----------------------------------------------------------------------------
  1017.     MergeGroupScrapData 
  1018.     
  1019.     Merge two blocks of group scrap data.
  1020.     
  1021.     Entry:    data1 = handle to first scrap data.
  1022.             data2 = handle to second scrap data.
  1023.             
  1024.     Exit:    function result = error code.
  1025.             data1 = handle to merged scrap data.
  1026. ----------------------------------------------------------------------------*/
  1027.  
  1028. static OSErr MergeGroupScrapData (Handle data1, Handle data2)
  1029. {
  1030.     long len1, len2;
  1031.     OSErr err = noErr;
  1032.     
  1033.     len1 = MyGetHandleSize(data1);
  1034.     len2 = MyGetHandleSize(data2);
  1035.     if (len2 < 16) {
  1036.         if (len1 < 4) return noErr;
  1037.         *(OSType*)(*data1) = 'XXXX';
  1038.         return noErr;
  1039.     }
  1040.     err = MySetHandleSize(data1, len1 + len2 - 16);
  1041.     if (err != noErr) return err;
  1042.     BlockMoveData(*data2 + 16, *data1 + len1, len2 - 16);
  1043.     *(long*)(*data1 + 12) += *(long*)(*data2 + 12);
  1044.     return noErr;
  1045. }
  1046.  
  1047.  
  1048.  
  1049. /*----------------------------------------------------------------------------
  1050.     GetGroupDragScrapData 
  1051.     
  1052.     Get the group scrap data at the end of a drag.
  1053.     
  1054.     Entry:    theDrag = drag reference.
  1055.             
  1056.     Exit:    function result = error code.
  1057.             *dragData = handle to drag data.
  1058. ----------------------------------------------------------------------------*/
  1059.  
  1060. OSErr GetGroupDragScrapData (DragReference theDrag, Handle *dragData)
  1061. {
  1062.     unsigned short numItems;
  1063.     ItemReference theItem;
  1064.     OSErr err = noErr;
  1065.     short i;
  1066.     Handle data = nil, itemData = nil;
  1067.     
  1068.     CountDragItems(theDrag, &numItems);
  1069.     for (i = 1; i <= numItems; i++) {
  1070.         err = GetDragItemReferenceNumber(theDrag, i, &theItem);
  1071.         if (err != noErr) goto exit;
  1072.         err = MyGetFlavorDataHandle(theDrag, theItem, kNewsWatcherSignature,
  1073.             &itemData);
  1074.         if (err != noErr) goto exit;
  1075.         if (i == 1) {
  1076.             data = itemData;
  1077.             itemData = nil;
  1078.         } else {
  1079.             err = MergeGroupScrapData(data, itemData);
  1080.             if (err != noErr) goto exit;
  1081.             MyDisposeHandle(itemData);
  1082.             itemData = nil;
  1083.         }
  1084.     }
  1085.     
  1086.     *dragData = data;
  1087.     return noErr;
  1088.     
  1089. exit:
  1090.  
  1091.     MyDisposeHandle(data);
  1092.     MyDisposeHandle(itemData);
  1093.     return err;
  1094. }
  1095.  
  1096.  
  1097.  
  1098. /*----------------------------------------------------------------------------
  1099.     HandleReceive 
  1100.     
  1101.     Drag Manager receive handler for user group list windows.
  1102.     
  1103.     Entry:    wind = pointer to user group list window.
  1104.             handlerRefCon = reference constant (nil).
  1105.             theDrag = drag reference.
  1106.             
  1107.     Exit:    function result = error code.
  1108.     
  1109.     Note: No user interaction or network transactions are permitted in
  1110.     this function.
  1111. ----------------------------------------------------------------------------*/
  1112.  
  1113. static pascal OSErr HandleReceive (WindowPtr wind, void *handlerRefCon, 
  1114.     DragReference theDrag)
  1115. {
  1116.     DragAttributes attributes;
  1117.     short mouseDownModifiers, mouseUpModifiers;
  1118.     
  1119.     if (gLongOperation || gInDialog || gDragErr != noErr ||
  1120.         gDragDestRow < 0 || !CanAcceptDrag(theDrag)) goto exit;
  1121.     
  1122.     GetDragAttributes(theDrag, &attributes);
  1123.     if ((attributes & dragInsideSenderApplication) == 0) gDragSrcWindow = nil;
  1124.     
  1125.     gDragDestWindow = wind;
  1126.     
  1127.     GetDragModifiers(theDrag, nil, &mouseDownModifiers, &mouseUpModifiers);
  1128.     gDragOptionKey = (mouseDownModifiers & optionKey) != 0 ||
  1129.         (mouseUpModifiers & optionKey) != 0;
  1130.         
  1131.     gFinalDragDestRow = gDragDestRow;
  1132.     
  1133.     if (gDragSrcWindow == nil) {
  1134.         gDragErr = GetGroupDragScrapData(theDrag, &gDragData);
  1135.         if (gDragErr != noErr) goto exit;
  1136.     }
  1137.         
  1138.     gDragPostProcessor = HandleReceivePostProcessor;
  1139.     
  1140.     return noErr;
  1141.     
  1142. exit:
  1143.  
  1144.     HandleTracking(dragTrackingLeaveWindow, wind, handlerRefCon, theDrag);
  1145.     return dragNotAcceptedErr;
  1146. }
  1147.  
  1148.  
  1149.  
  1150. /*----------------------------------------------------------------------------
  1151.     AddDragItems
  1152.     
  1153.     Add the drag items for dragging groups.
  1154.     
  1155.     Entry:    dragRef = drag reference.
  1156.     
  1157.     Exit:    function result = error code.
  1158. ----------------------------------------------------------------------------*/
  1159.  
  1160. static OSErr AddDragItems (DragReference dragRef)
  1161. {
  1162.     Handle privateScrap = nil, publicScrap = nil;
  1163.     OSErr err = noErr;
  1164.     
  1165.     err = BuildGroupScrapData(gDragSrcWindow, &privateScrap, &publicScrap);
  1166.     if (err != noErr) goto exit;
  1167.     
  1168.     MyHLock(privateScrap);
  1169.     err = AddDragItemFlavor(dragRef, 1, kNewsWatcherSignature, *privateScrap,
  1170.         MyGetHandleSize(privateScrap), 0);
  1171.     if (err != noErr) goto exit;
  1172.     MyDisposeHandle(privateScrap);
  1173.     privateScrap = nil;
  1174.     
  1175.     MyHLock(publicScrap);
  1176.     err = AddDragItemFlavor(dragRef, 1, 'TEXT', *publicScrap,
  1177.         MyGetHandleSize(publicScrap), 0);
  1178.     if (err != noErr) goto exit;
  1179.     MyDisposeHandle(publicScrap);
  1180.     
  1181.     return noErr;
  1182.  
  1183. exit:
  1184.  
  1185.     MyDisposeHandle(privateScrap);
  1186.     MyDisposeHandle(publicScrap);
  1187.     return err;
  1188. }
  1189.  
  1190.  
  1191.  
  1192. /*----------------------------------------------------------------------------
  1193.     CheckForDragToTrash
  1194.     
  1195.     If the user dragged groups to the trash, unsubscribe.
  1196.     
  1197.     Entry:    dragRef = drag reference.
  1198. ----------------------------------------------------------------------------*/
  1199.  
  1200. static void CheckForDragToTrash (DragReference dragRef)
  1201. {
  1202.     TWindow **info;
  1203.     
  1204.     info = (TWindow**)GetWRefCon(gDragSrcWindow);
  1205.     if ((**info).groupKind != kUserGroup) return;
  1206.     
  1207.     if (!DragTargetWasTrash(dragRef)) return;
  1208.     
  1209.     UnsubscribeSelected(gDragSrcWindow);
  1210. }
  1211.  
  1212.  
  1213.  
  1214. /*----------------------------------------------------------------------------
  1215.     GroupListClickLoop
  1216.     
  1217.     The click loop routine for group lists when we have the Drag Manager.
  1218.     It initiates group drags.
  1219.     
  1220.     Exit:    function result = true if mouse button still down, false
  1221.                 if mouse button released.
  1222. ----------------------------------------------------------------------------*/
  1223.  
  1224. static Boolean GroupListClickLoop (void)
  1225. {    
  1226.     TWindow **info;
  1227.     ListHandle theList;
  1228.     OSErr err = noErr;
  1229.     DragReference dragRef;
  1230.     Boolean haveDragRef = false;
  1231.     RgnHandle dragRgn = nil;
  1232.     
  1233.     info = (TWindow**)GetWRefCon(gDragSrcWindow);
  1234.     theList = (**info).theList;
  1235.  
  1236.     if (gFirstListClickCall) {
  1237.         gFirstListClickCall = false;
  1238.         return true;
  1239.     }
  1240.     
  1241.     if (WaitMouseMoved(gCurEvent.where)) {
  1242.         gDidDrag = true;
  1243.         err = NewDrag(&dragRef);
  1244.         if (err != noErr) goto exit;
  1245.         haveDragRef = true;
  1246.         err = AddDragItems(dragRef);
  1247.         if (err != noErr) goto exit;
  1248.         BuildListSelectedCellsDragRegion(theList, &dragRgn);
  1249.         gDragDestRow = -1;
  1250.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1251.         if (err != noErr) goto exit;
  1252.         DisposeRgn(dragRgn);
  1253.         CheckForDragToTrash(dragRef);
  1254.         DisposeDrag(dragRef);
  1255.     }
  1256.     
  1257.     return false;
  1258.     
  1259. exit:
  1260.  
  1261.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1262.     if (haveDragRef) DisposeDrag(dragRef);
  1263.     gClickLoopErr = err;
  1264.     return false;
  1265. }
  1266.  
  1267.  
  1268.  
  1269. /*----------------------------------------------------------------------------
  1270.     MakeNewWindow 
  1271.     
  1272.     Create a new group window.
  1273.     
  1274.     Entry:    groupKind = kind of group window.
  1275.             title = window title.
  1276.             
  1277.     Exit:    function result = error code.
  1278.             *theWindow = pointer to new window.
  1279. ----------------------------------------------------------------------------*/
  1280.  
  1281. static OSErr MakeNewWindow (TGroupWindowKind groupKind, StringPtr title, 
  1282.     WindowPtr *theWindow)
  1283. {
  1284.     short width, height;
  1285.     WindowPtr wind = nil;
  1286.     TWindow **info;
  1287.     ListHandle theList;
  1288.     Point thePt;
  1289.     Rect listRect, sizeRect;
  1290.     short panelHeight;
  1291.     OSErr err = noErr;
  1292.     GrafPtr port;
  1293.     
  1294.     GetPort(&port);
  1295.     
  1296.     MyICReadSharedPrefs(kICListFont);
  1297.  
  1298.     err = CreateNewWindow(kGroup, title, gPrefs.listFont, gPrefs.listSize, &wind);
  1299.     if (err != noErr) return err;
  1300.     SetPort(wind);
  1301.     info = (TWindow**)GetWRefCon(wind);
  1302.     panelHeight = GetFontLineHeight(wind) + 9;
  1303.     (**info).panelHeight = panelHeight;
  1304.     (**info).groupKind = groupKind;
  1305.     width = kMinWindowWidth;
  1306.     height = MinHeight(wind);
  1307.     PositionNewWindow(wind, width, height);
  1308.  
  1309.     SetPt(&thePt, 0, 0);
  1310.     SetRect(&sizeRect, 0, 0, 1, 0);
  1311.     listRect = wind->portRect;
  1312.     listRect.top += panelHeight;
  1313.     listRect.right -= 15;
  1314.     listRect.bottom -= 15;
  1315.     theList = MyLNew(&listRect, &sizeRect, thePt, kLDEFProc, wind, false, 
  1316.         true, false, true);
  1317.     (**theList).indent.h = 4;
  1318.     (**theList).selFlags |= lNoNilHilite;
  1319.     (**theList).refCon = (long)gListDefFuncUPP;
  1320.     (**info).theList = theList;
  1321.     (**info).vScroll = (**theList).vScroll;
  1322.     
  1323.     if (groupKind == kUserGroup && gHaveDragMgr) {
  1324.         err = InstallTrackingHandler(gHandleTrackingUPP, wind, nil);
  1325.         if (err != noErr) goto exit;
  1326.         err = InstallReceiveHandler(gHandleReceiveUPP, wind, nil);
  1327.         if (err != noErr) goto exit;
  1328.     }
  1329.     
  1330.     *theWindow = wind;
  1331.     SetPort(port);
  1332.     return noErr;
  1333.     
  1334. exit:
  1335.  
  1336.     DoClose(wind);
  1337.     return err;
  1338. }
  1339.  
  1340.  
  1341.  
  1342. /*----------------------------------------------------------------------------
  1343.     MakeUserGroupWindow 
  1344.     
  1345.     Create a new user group window.
  1346.     
  1347.     Entry:    title = window title.
  1348.             groupArray = handle to group array, or nil to create an empty one.
  1349.             numGroups = number of groups in group array.
  1350.             pos = pointer to saved window position.
  1351.             
  1352.     Exit:    function result = error code.
  1353.             *theWindow = pointer to new window.
  1354. ----------------------------------------------------------------------------*/
  1355.  
  1356. OSErr MakeUserGroupWindow (StringPtr title, TGroup **groupArray, short numGroups,
  1357.     TSavedWindPos *pos, WindowPtr *theWindow)
  1358. {
  1359.     WindowPtr wind = nil;
  1360.     TWindow **info;
  1361.     ListHandle theList;
  1362.     Point theCell;
  1363.     short cellDataLen, groupIndex, numCells, wid;
  1364.     OSErr err = noErr;
  1365.     GrafPtr port;
  1366.     Boolean needsZooming;
  1367.     
  1368.     GetPort(&port);
  1369.  
  1370.     err = MakeNewWindow(kUserGroup, title, &wind);
  1371.     if (err != noErr) return err;
  1372.     SetPort(wind);
  1373.     info = (TWindow**)GetWRefCon(wind);
  1374.     theList = (**info).theList;
  1375.     if (groupArray == nil) {
  1376.         err = MyNewHandle(0, &groupArray);
  1377.         if (err != noErr) goto exit;
  1378.         (**info).groupArray = groupArray;
  1379.     } else {
  1380.         (**info).groupArray = groupArray;
  1381.         (**info).numGroups = numGroups;
  1382.         err = MakeGroupList(numGroups, theList);
  1383.         if (err != noErr) goto exit;
  1384.         theCell.h = 0;
  1385.         numCells = (**theList).dataBounds.bottom;
  1386.         for (theCell.v = 0; theCell.v < numCells; theCell.v++) {
  1387.             cellDataLen = 2;
  1388.             LGetCell(&groupIndex, &cellDataLen, theCell, theList);
  1389.             if ((*groupArray)[groupIndex].numUnread > 0) break;
  1390.         }
  1391.         if (theCell.v < numCells) {
  1392.             MyLSetSelect(true, theCell, theList);
  1393.             MyLAutoScroll(theList);
  1394.         }
  1395.     }
  1396.     wid = CharWidth('9');
  1397.     (**info).groupNameHCoord = 6 * wid;
  1398.     (**info).numUnreadHCoord = 4 * wid;
  1399.  
  1400.     RestoreWindPos(wind, pos, &needsZooming);
  1401.     ResizeContents(wind);
  1402.     if (needsZooming) {
  1403.         err = DoZoom(wind, inZoomOut);
  1404.         if (err != noErr) goto exit;
  1405.     }
  1406.     (**info).movedSinceLastSave = false;
  1407.     
  1408.     MyShowWindow(wind);
  1409.     *theWindow = wind;
  1410.     SetPort(port);
  1411.     return noErr;
  1412.  
  1413. exit:
  1414.  
  1415.     DoClose(wind);
  1416.     SetPort(port);
  1417.     return err;
  1418. }
  1419.  
  1420.  
  1421.  
  1422. /*----------------------------------------------------------------------------
  1423.     MakeNewUntitledUserGroupWindow 
  1424.     
  1425.     Create a new untitled user group list window.
  1426.     
  1427.     Exit:    function result = error code.
  1428.             *wind = pointer to new window.
  1429. ----------------------------------------------------------------------------*/
  1430.  
  1431. OSErr MakeNewUntitledUserGroupWindow (WindowPtr *wind)
  1432. {
  1433.     CStr255 untitled, title, xTitle;
  1434.     WindowPtr theWind;
  1435.     Boolean conflict;
  1436.     TSavedWindPos pos;
  1437.     short i;
  1438.     
  1439.     GetCString(kStrUntitled, untitled);
  1440.  
  1441.     for (i = 1; ; i++) {
  1442.         if (i == 1) {
  1443.             strcpy(title, untitled);
  1444.         } else {
  1445.             sprintf(title, "%s %d", untitled, i);
  1446.         }
  1447.         conflict = false;
  1448.         theWind = FrontWindow();
  1449.         while (theWind != nil) {
  1450.             GetWTitle(theWind, (StringPtr)xTitle);
  1451.             p2cstr((StringPtr)xTitle);
  1452.             if (strcmp(title, xTitle) == 0) {
  1453.                 conflict = true;
  1454.                 break;
  1455.             }
  1456.             theWind = (WindowPtr)((WindowPeek)theWind)->nextWindow;
  1457.         }
  1458.         if (!conflict) break;
  1459.     }
  1460.  
  1461.     c2pstr(title);
  1462.     pos.valid = false;
  1463.     return MakeUserGroupWindow((StringPtr)title, nil, 0, &pos, wind);
  1464. }
  1465.  
  1466.  
  1467.  
  1468. /*----------------------------------------------------------------------------
  1469.     MakeNewGroupsWindow 
  1470.     
  1471.     Create a new groups list window.
  1472.     
  1473.      Entry:    groupArray = handle to group array.
  1474.             numGroups = number of groups in group array.
  1475.  
  1476.     Exit:    function result = error code.
  1477.             *theWindow = pointer to new window.
  1478. ----------------------------------------------------------------------------*/
  1479.  
  1480. OSErr MakeNewGroupsWindow (TGroup **groupArray, short numGroups, WindowPtr *theWindow)
  1481. {
  1482.     WindowPtr wind = nil;
  1483.     TWindow **info;
  1484.     ListHandle theList;
  1485.     Cell theCell;
  1486.     Str255 title;
  1487.     OSErr err = noErr;
  1488.     GrafPtr port;
  1489.     
  1490.     GetPort(&port);
  1491.     
  1492.     GetPString(kStrNewGroups, title);
  1493.     err = MakeNewWindow(kNewGroup, title, &wind);
  1494.     if (err != noErr) return err;
  1495.     SetPort(wind);
  1496.     info = (TWindow**)GetWRefCon(wind);
  1497.     theList = (**info).theList;
  1498.     (**info).numGroups = numGroups;
  1499.     (**info).groupArray = groupArray;
  1500.     
  1501.     err = MakeGroupList(numGroups, theList);
  1502.     if (err != noErr) goto exit;
  1503.     
  1504.     SetPt(&theCell,0,0);
  1505.     MyLSetSelect(true, theCell, theList);
  1506.     
  1507.     err = DoZoom(wind, inZoomOut);
  1508.     if (err != noErr) goto exit;
  1509.     MyShowWindow(wind);
  1510.     *theWindow = wind;
  1511.     SetPort(port);
  1512.     return noErr;
  1513.     
  1514. exit:
  1515.  
  1516.     DoClose(wind);
  1517.     SetPort(port);
  1518.     return err;
  1519. }
  1520.  
  1521.  
  1522.  
  1523. /*----------------------------------------------------------------------------
  1524.     MakeFullGroupWindow 
  1525.     
  1526.     Create the full group list window.
  1527.     
  1528.      Entry:    groupArray = handle to full group array.
  1529.             numGroups = number of groups in full group array.
  1530.  
  1531.     Exit:    function result = error code.
  1532.             *theWindow = pointer to new window.
  1533. ----------------------------------------------------------------------------*/
  1534.  
  1535. OSErr MakeFullGroupWindow (TGroup **groupArray, short numGroups, WindowPtr *theWindow)
  1536. {
  1537.     WindowPtr wind = nil;
  1538.     TWindow **info;
  1539.     ListHandle theList;
  1540.     Cell theCell;
  1541.     Str255 title;
  1542.     OSErr err = noErr;
  1543.     GrafPtr port;
  1544.     Boolean needsZooming;
  1545.         
  1546.     GetPort(&port);
  1547.  
  1548.     GetPString(kStrFullGroupList, title);
  1549.     err = MakeNewWindow(kFullGroup, title, &wind);
  1550.     if (err != noErr) return err;
  1551.     SetPort(wind);
  1552.     info = (TWindow**)GetWRefCon(wind);
  1553.     theList = (**info).theList;    
  1554.     (**info).numGroups = numGroups;
  1555.     (**info).groupArray = groupArray;
  1556.     
  1557.     err = MakeGroupList(numGroups, theList);
  1558.     if (err != noErr) goto exit;
  1559.     
  1560.     SetPt(&theCell, 0, 0);
  1561.     MyLSetSelect(true, theCell, theList);
  1562.  
  1563.     RestoreWindPos(wind, &gPrefs.fullGroupWindPos, &needsZooming);
  1564.     ResizeContents(wind);
  1565.     
  1566.     if (gPrefs.fullGroupListVisible) {
  1567.         if (needsZooming) {
  1568.             err = DoZoom(wind, inZoomOut);
  1569.             if (err != noErr) goto exit;
  1570.         }
  1571.         gMustDoZoomOnShowFullGroupList = false;
  1572.         MyShowWindow(wind);
  1573.         SetWindowsMenuShowHideFullGroupList(false);
  1574.     } else {
  1575.         gMustDoZoomOnShowFullGroupList = needsZooming;
  1576.     }
  1577.     
  1578.     *theWindow = wind;
  1579.     SetPort(port);
  1580.     return noErr;
  1581.     
  1582. exit:
  1583.  
  1584.     DoClose(wind);
  1585.     SetPort(port);
  1586.     return err;
  1587. }    
  1588.  
  1589.  
  1590.  
  1591. /*----------------------------------------------------------------------------
  1592.     OpenSelectedCells 
  1593.     
  1594.     Open all the selected groups in a group list window.
  1595.     
  1596.     Entry:    wind = pointer to group list window.
  1597.             promptMaxFetch = true to prompt for maximum number of
  1598.                 articles to fetch.
  1599.                 
  1600.     Exit:    function result = error code.
  1601. ----------------------------------------------------------------------------*/
  1602.  
  1603. static OSErr OpenSelectedCells (WindowPtr wind, Boolean promptMaxFetch)
  1604. {
  1605.     Cell theCell;
  1606.     TWindow **info;
  1607.     ListHandle theList;
  1608.     short *p, *pBegin, numSelected=0, numOpened=0;
  1609.     long maxFetch;
  1610.     DialogPtr dlg = nil;
  1611.     short item;
  1612.     OSErr err = noErr;
  1613.     Boolean hasArts;
  1614.     
  1615.     info = (TWindow**)GetWRefCon(wind);
  1616.     theList = (**info).theList;
  1617.     
  1618.     maxFetch = gPrefs.maxFetch;
  1619.     if (promptMaxFetch) {
  1620.         err = MyGetNewDialog(kMaxFetchDialog, ok, cancel, &dlg);
  1621.         if (err != noErr) return err;
  1622.         SetItemNumeric(dlg, kMaxArticles);
  1623.         SetItemMaxLength(dlg, kMaxArticles, 5);
  1624.         SelectDialogItemText(dlg, kMaxArticles, 0, 0);
  1625.         MyModalDialog(dlg, gDialogFilterUPP, &item);
  1626.         maxFetch = DlgGetNumber(dlg, kMaxArticles);
  1627.         if (maxFetch > 15000) maxFetch = 15000;
  1628.         if (maxFetch <= 0) maxFetch = 1;
  1629.         err = DoClose(dlg);
  1630.         if (err != noErr) return err;
  1631.         dlg = nil;
  1632.         if (item == cancel) return userCanceledErr;
  1633.         if (maxFetch == 0) maxFetch = gPrefs.maxFetch;
  1634.         err = MarkSelectedGroups(wind, false, maxFetch);
  1635.         if (err != noErr) return err;
  1636.     }
  1637.     
  1638.     SetPt(&theCell, 0, (**theList).dataBounds.bottom-1);
  1639.     while (theCell.v >= 0) {
  1640.         pBegin = (**theList).cellArray;
  1641.         p = pBegin + theCell.v;
  1642.         while (*p >= 0 && p >= pBegin) p--;
  1643.         theCell.v = p - pBegin;
  1644.         if (p >= pBegin) {
  1645.             err = OpenGroupCell(wind, theCell, maxFetch, &hasArts);
  1646.             if (err != noErr) return err;
  1647.             numSelected++;
  1648.             if (hasArts) numOpened++;
  1649.         }
  1650.         theCell.v--;
  1651.     }
  1652.     if (numOpened < numSelected) {
  1653.         if (numSelected == 1) {
  1654.             if ((**info).groupKind == kUserGroup) {
  1655.                 NoteMessageNumber(kStrSelGroupNoUnread);
  1656.             } else {
  1657.                 NoteMessageNumber(kStrSelGroupNoArt);
  1658.             }
  1659.         } else if (numOpened == 0) {
  1660.             if ((**info).groupKind == kUserGroup) {
  1661.                 NoteMessageNumber(kStrNoneUnread);
  1662.             } else {
  1663.                 NoteMessageNumber(kStrNoArts);
  1664.             }
  1665.         }
  1666.     }
  1667.     return noErr;
  1668. }
  1669.  
  1670.  
  1671.  
  1672. /*----------------------------------------------------------------------------
  1673.     DoNewGroupWindow 
  1674.     
  1675.     Handle the "New Group Window" command.
  1676.     
  1677.     Exit:    function result = error code.
  1678. ----------------------------------------------------------------------------*/
  1679.  
  1680. OSErr DoNewGroupWindow (void)
  1681. {
  1682.     WindowPtr wind;
  1683.     
  1684.     return MakeNewUntitledUserGroupWindow(&wind);
  1685. }
  1686.  
  1687.  
  1688.  
  1689. /*----------------------------------------------------------------------------
  1690.     DoCut 
  1691.     
  1692.     Handle the "Cut" command for a user group window.
  1693.             
  1694.     Entry:    wind = pointer to user group window.
  1695.     
  1696.     Exit:    function result = error code.
  1697. ----------------------------------------------------------------------------*/
  1698.  
  1699. static OSErr DoCut (WindowPtr wind)
  1700. {
  1701.     OSErr err = noErr;
  1702.     OSErr DoCopy (WindowPtr wind);
  1703.     
  1704.     err = DoCopy(wind);
  1705.     if (err != noErr) return err;
  1706.     
  1707.     return UnsubscribeSelected(wind);
  1708. }
  1709.  
  1710.  
  1711.  
  1712. /*----------------------------------------------------------------------------
  1713.     DoCopy 
  1714.     
  1715.     Handle the "Copy" command for a group window.
  1716.             
  1717.     Entry:    wind = pointer to group window.
  1718.     
  1719.     Exit:    function result = error code.
  1720. ----------------------------------------------------------------------------*/
  1721.  
  1722. static OSErr DoCopy (WindowPtr wind)
  1723. {
  1724.     OSErr err = noErr;
  1725.     Handle privateScrap = nil;
  1726.     Handle publicScrap = nil;
  1727.  
  1728.     err = BuildGroupScrapData(wind, &privateScrap, &publicScrap);
  1729.     if (err != noErr) return err;
  1730.     
  1731.     ZeroScrap();
  1732.     
  1733.     MyHLockHi(privateScrap);
  1734.     PutScrap(MyGetHandleSize(privateScrap), kNewsWatcherSignature, *privateScrap);
  1735.     MyDisposeHandle(privateScrap);
  1736.     
  1737.     MyHLockHi(publicScrap);
  1738.     PutScrap(MyGetHandleSize(publicScrap), 'TEXT', *publicScrap);
  1739.     MyDisposeHandle(publicScrap);
  1740.     
  1741.     return noErr;
  1742. }
  1743.  
  1744.  
  1745.  
  1746. /*----------------------------------------------------------------------------
  1747.     DoPaste 
  1748.     
  1749.     Handle the "Paste" command for a user group window.
  1750.             
  1751.     Entry:    wind = pointer to user group window.
  1752.     
  1753.     Exit:    function result = error code.
  1754. ----------------------------------------------------------------------------*/
  1755.  
  1756. static OSErr DoPaste (WindowPtr wind)
  1757. {
  1758.     Handle scrapData = nil;
  1759.     OSErr err = noErr;
  1760.     long scrapOffset;
  1761.     
  1762.     err = MyNewHandle(0, &scrapData);
  1763.     if (err != noErr) return err;
  1764.     if (GetScrap(scrapData, kNewsWatcherSignature, &scrapOffset) <= 0) goto exit;
  1765.     err = CopyGroupsFromScrap(scrapData, wind, 0x7fff);
  1766.     if (err != noErr) goto exit;
  1767.     MyDisposeHandle(scrapData);
  1768.     return noErr;
  1769.     
  1770. exit:
  1771.  
  1772.     MyDisposeHandle(scrapData);
  1773.     return err;
  1774. }
  1775.  
  1776.  
  1777.  
  1778. /*----------------------------------------------------------------------------
  1779.     DoClear 
  1780.     
  1781.     Handle the "Clear" command for a user group window.
  1782.             
  1783.     Entry:    wind = pointer to user group window.
  1784.     
  1785.     Exit:    function result = error code.
  1786. ----------------------------------------------------------------------------*/
  1787.  
  1788. static OSErr DoClear (WindowPtr wind)
  1789. {
  1790.     return UnsubscribeSelected(wind);
  1791. }
  1792.  
  1793.  
  1794.  
  1795. /*----------------------------------------------------------------------------
  1796.     DoSelectAll 
  1797.     
  1798.     Handle the "Select All" command for a group window.
  1799.             
  1800.     Entry:    wind = pointer to group window.
  1801. ----------------------------------------------------------------------------*/
  1802.  
  1803. static void DoSelectAll (WindowPtr wind)
  1804. {
  1805.     TWindow **info;
  1806.     
  1807.     info = (TWindow**)GetWRefCon(wind);
  1808.     SelectOrDeselectAllListItems((**info).theList, true);
  1809. }
  1810.  
  1811.  
  1812.  
  1813. /*----------------------------------------------------------------------------
  1814.     DoDeselectAll 
  1815.     
  1816.     Handle the "Deselect All" command for a group window.
  1817.             
  1818.     Entry:    wind = pointer to group window.
  1819. ----------------------------------------------------------------------------*/
  1820.  
  1821. static void DoDeselectAll (WindowPtr wind)
  1822. {
  1823.     TWindow **info;
  1824.     
  1825.     info = (TWindow**)GetWRefCon(wind);
  1826.     SelectOrDeselectAllListItems((**info).theList, false);
  1827. }
  1828.  
  1829.  
  1830.  
  1831. /*----------------------------------------------------------------------------
  1832.     DoFind 
  1833.     
  1834.     Handle the "Find" command for a group window.
  1835.             
  1836.     Entry:    wind = pointer to group window.
  1837.     
  1838.     Exit:    function result = error code.
  1839. ----------------------------------------------------------------------------*/
  1840.  
  1841. static OSErr DoFind (WindowPtr wind)
  1842. {
  1843.     TWindow **info;
  1844.     ListHandle theList;
  1845.     Cell theCell;
  1846.     OSErr err = noErr;
  1847.     
  1848.     err = DoFindDialog();
  1849.     if (err != noErr) return err;
  1850.     info = (TWindow**)GetWRefCon(wind);
  1851.     theList = (**info).theList;
  1852.     SetPt(&theCell, 0, 0);
  1853.     if (!gPrefs.startFindAtBeginning) LGetSelect(true, &theCell, theList);
  1854.     return Find(wind, theCell);
  1855. }
  1856.  
  1857.  
  1858.  
  1859. /*----------------------------------------------------------------------------
  1860.     DoFindAgain
  1861.     
  1862.     Handle the "Find Again" command for a group window.
  1863.             
  1864.     Entry:    wind = pointer to group window.
  1865.     
  1866.     Exit:    function result = error code.
  1867. ----------------------------------------------------------------------------*/
  1868.  
  1869. static OSErr DoFindAgain (WindowPtr wind)
  1870. {
  1871.     TWindow **info;
  1872.     ListHandle theList;
  1873.     Cell theCell;
  1874.     
  1875.     info = (TWindow**)GetWRefCon(wind);
  1876.     theList = (**info).theList;
  1877.     SetPt(&theCell, 0, 0);
  1878.     if (LGetSelect(true, &theCell, theList)) theCell.v++;
  1879.     return Find(wind, theCell);
  1880. }
  1881.  
  1882.  
  1883.  
  1884. /*----------------------------------------------------------------------------
  1885.     DoSubscribe 
  1886.     
  1887.     Handle the "Subscribe" command for a group window.
  1888.             
  1889.     Entry:    wind = pointer to full or new group window.
  1890.     
  1891.     Exit:    function result = error code.
  1892. ----------------------------------------------------------------------------*/
  1893.  
  1894. static OSErr DoSubscribe (WindowPtr wind)
  1895. {
  1896.     WindowPtr targetWind;
  1897.     TWindow **targetInfo;
  1898.     TWindowKind kind;
  1899.     OSErr err = noErr;
  1900.  
  1901.     targetWind = FrontWindow();
  1902.     while (targetWind != nil) {
  1903.         kind = GetMyWindowKind(targetWind);
  1904.         if (kind == kGroup) {
  1905.             targetInfo = (TWindow**)GetWRefCon(targetWind);
  1906.             if ((**targetInfo).groupKind == kUserGroup) break;
  1907.         }
  1908.         targetWind = (WindowPtr)((WindowPeek)targetWind)->nextWindow;
  1909.     }
  1910.     
  1911.     if (targetWind == nil) {
  1912.         err = MakeNewUntitledUserGroupWindow(&targetWind);
  1913.         if (err != noErr) return err;
  1914.     }
  1915.     
  1916.     return CopyOrMoveSelectedGroups(wind, targetWind, 0x7fff, true);
  1917. }
  1918.  
  1919.  
  1920.  
  1921. /*----------------------------------------------------------------------------
  1922.     DoUnsubscribe 
  1923.     
  1924.     Handle the "Unsubscribe" command for a user group window.
  1925.             
  1926.     Entry:    wind = pointer to group window.
  1927.     
  1928.     Exit:    function result = error code.
  1929. ----------------------------------------------------------------------------*/
  1930.  
  1931. static OSErr DoUnsubscribe (WindowPtr wind)
  1932. {
  1933.     return UnsubscribeSelected(wind);
  1934. }
  1935.  
  1936.  
  1937.  
  1938. /*----------------------------------------------------------------------------
  1939.     Activate 
  1940.     
  1941.     Handle an activate event for a group window.
  1942.             
  1943.     Entry:    wind = pointer to group window.
  1944.             act = true to activate, false to deactivate
  1945. ----------------------------------------------------------------------------*/
  1946.  
  1947. static void Activate (WindowPtr wind, Boolean act)
  1948. {
  1949.     TWindow **info;
  1950.     Rect r;
  1951.     
  1952.     info = (TWindow**)GetWRefCon(wind);
  1953.     MyLActivate(act, (**info).theList);
  1954.     r = wind->portRect;
  1955.     r.top = r.bottom - 15;
  1956.     r.left = r.right - 15;
  1957.     InvalRect(&r);
  1958. }
  1959.  
  1960.  
  1961.  
  1962. /*----------------------------------------------------------------------------
  1963.     Update 
  1964.     
  1965.     Handle an update event for a group window.
  1966.             
  1967.     Entry:    wind = pointer to group window.
  1968. ----------------------------------------------------------------------------*/
  1969.  
  1970. static void Update (WindowPtr wind)
  1971. {
  1972.     TWindow **info;
  1973.     ListHandle theList;
  1974.     short panelHeight, windWidth, numGroups;
  1975.     Rect r;
  1976.     FontInfo fontInfo;
  1977.     CStr255 str;
  1978.     CStr255 fmt;
  1979.  
  1980.     info = (TWindow**)GetWRefCon(wind);
  1981.     theList = (**info).theList;
  1982.     panelHeight = (**info).panelHeight;
  1983.     
  1984.     r = wind->portRect;
  1985.     r.top += panelHeight;
  1986.     ClipRect(&r);
  1987.     DrawGrowIcon(wind);
  1988.     ClipRect(&wind->portRect);
  1989.     
  1990.     windWidth = wind->portRect.right - wind->portRect.left;
  1991.     MoveTo(0, panelHeight-3);
  1992.     LineTo(windWidth, panelHeight-3);
  1993.     MoveTo(0, panelHeight-1);
  1994.     LineTo(windWidth, panelHeight-1);
  1995.     
  1996.     GetFontInfo(&fontInfo);
  1997.     numGroups = (**theList).dataBounds.bottom;
  1998.     if (numGroups == 1) {
  1999.         GetCString(kStrOneGroup, str);
  2000.     } else {
  2001.         GetCString(kStrNGroups, fmt);
  2002.         sprintf(str, fmt, numGroups);
  2003.     }
  2004.     c2pstr(str);
  2005.     TruncString(windWidth - 20, (StringPtr)str, smTruncEnd);
  2006.     MoveTo(10, fontInfo.ascent + 3);
  2007.     DrawString((StringPtr)str);
  2008.     
  2009.     LUpdate(wind->visRgn, (**info).theList);
  2010. }
  2011.  
  2012.  
  2013.  
  2014. /*----------------------------------------------------------------------------
  2015.     Mouse 
  2016.     
  2017.     Handle a mouse down event in the content area of a group window.
  2018.             
  2019.     Entry:    wind = pointer to group window.
  2020.             where = location of mouse down in local coords.
  2021.             modifiers = modifiers field from event record.
  2022.             
  2023.     Exit:    function result = error code.
  2024. ----------------------------------------------------------------------------*/
  2025.  
  2026. static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
  2027. {
  2028.     TWindow **info;
  2029.     ListHandle theList;
  2030.     OSErr err = noErr;
  2031.     Boolean shift, command, option, doubleClick;
  2032.     Rect rView;
  2033.  
  2034.     info = (TWindow**) GetWRefCon(wind);
  2035.     theList = (**info).theList;
  2036.     
  2037.     shift = (modifiers & shiftKey) != 0;
  2038.     command = (modifiers & cmdKey) != 0;
  2039.     option = (modifiers & optionKey) != 0;
  2040.     
  2041.     if (where.v < (**info).panelHeight) {
  2042.         SelectOrDeselectAllListItems(theList, false);
  2043.         return noErr;
  2044.     }
  2045.     
  2046.     rView = (**theList).rView;
  2047.     if (where.v <= rView.top || where.v >= rView.bottom) return noErr;
  2048.     if (where.v >= rView.bottom - 1) where.v = rView.bottom - 2;
  2049.         
  2050.     if (!shift && !command && PtInListCell(where, theList)) {
  2051.         if (gHaveDragMgr) {
  2052.             gDragSrcWindow = wind;
  2053.             gFirstListClickCall = true;
  2054.             gClickLoopErr = noErr;
  2055.             (**theList).lClickLoop = gGroupListClickLoopUPP;
  2056.         } else {
  2057.             OldBeginListClick(nil);
  2058.             (**theList).lClickLoop = gOldListClickLoopUPP;
  2059.         }
  2060.         gDidDrag = false;
  2061.         doubleClick = MyLClickPossiblyInactive(where, modifiers, theList);
  2062.         if (doubleClick && !gDidDrag) {
  2063.             err = OpenSelectedCells(wind, option);
  2064.             if (err != noErr) return err;
  2065.         } else if (gHaveDragMgr) {
  2066.             if (gClickLoopErr != noErr) return gClickLoopErr;
  2067.         } else {
  2068.             err = OldEndListClick();
  2069.             if (err != noErr) return err;
  2070.         }
  2071.     } else {
  2072.         (**theList).lClickLoop = nil;
  2073.         MyLClickPossiblyInactive(where, modifiers, theList);
  2074.     }
  2075.     
  2076.     return noErr;
  2077. }
  2078.  
  2079.  
  2080.  
  2081. /*----------------------------------------------------------------------------
  2082.     Draggable
  2083.     
  2084.     Determine whether a mouse down event is on a draggable object in a 
  2085.     group window.
  2086.     
  2087.     Entry:    wind = pointer to group window.
  2088.             where = location of mouse down event, in local coordinates.
  2089.             modifiers = modifiers field from event record.
  2090.             
  2091.     Exit:    function result = true if object is draggable.
  2092. ----------------------------------------------------------------------------*/
  2093.  
  2094. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  2095. {
  2096.     TWindow **info;
  2097.     ListHandle theList;
  2098.  
  2099.     if ((modifiers & shiftKey) != 0) return false;
  2100.     if ((modifiers & cmdKey) != 0) return false;
  2101.     info = (TWindow**)GetWRefCon(wind);
  2102.     theList = (**info).theList;
  2103.     return PtInListCell(where, theList);
  2104. }
  2105.  
  2106.  
  2107.  
  2108. /*----------------------------------------------------------------------------
  2109.     Key 
  2110.     
  2111.     Handle a key down event for a group window.
  2112.             
  2113.     Entry:    wind = pointer to group window.
  2114.             theChar = ASCII code of key.
  2115.             theKey = keyboard code of key.
  2116.             modifiers = modifiers field from event record.
  2117.             
  2118.     Exit:    function result = error code.
  2119. ----------------------------------------------------------------------------*/
  2120.  
  2121. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  2122.     short modifiers)
  2123. {
  2124.     TWindow **info;
  2125.     ListHandle theList;
  2126.     Cell scrollIntoView;
  2127.     OSErr err = noErr;
  2128.     TKeypadKey keypadKey;
  2129.     Boolean isUpOrDownArrow;
  2130.     
  2131.     info = (TWindow**)GetWRefCon(wind);
  2132.     theList = (**info).theList;
  2133.     isUpOrDownArrow = theChar == upArrow || theChar == downArrow;
  2134.     
  2135.     if ((modifiers & cmdKey) != 0 && !isUpOrDownArrow) {
  2136.         SysBeep(0);
  2137.         return noErr;
  2138.     }
  2139.  
  2140.     if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  2141.         switch (keypadKey) {
  2142.             case kKeypadEqualKey:
  2143.                 DoSelectAll(wind);
  2144.                 return noErr;
  2145.             case kKeypadStarKey:
  2146.                 return DoClose(wind);
  2147.             case kKeypadMinusKey:
  2148.                 return DoMarkCommand(wind, false);
  2149.             case kKeypadPlusKey:
  2150.                 return DoMarkCommand(wind, true);
  2151.             case kKeypadEnterKey:
  2152.                 return DoNextGroup(wind);
  2153.             case kKeypadPeriodKey:
  2154.                 return DoNextThread(wind);
  2155.             case kKeypad0Key:
  2156.                 return DoNextArticle(wind);
  2157.             case kKeypad1Key:
  2158.                 Scroll(wind, kScrollToEnd);
  2159.                 return noErr;
  2160.             case kKeypad2Key:
  2161.                 Scroll(wind, inDownButton);
  2162.                 return noErr;
  2163.             case kKeypad3Key:
  2164.                 Scroll(wind, inPageDown);
  2165.                 return noErr;
  2166.             case kKeypad5Key:
  2167.                 return DoNextArticle(wind);
  2168.             case kKeypad7Key:
  2169.                 Scroll(wind, kScrollToHome);
  2170.                 return noErr;
  2171.             case kKeypad8Key:
  2172.                 Scroll(wind, inUpButton);
  2173.                 return noErr;
  2174.             case kKeypad9Key:
  2175.                 Scroll(wind, inPageUp);
  2176.                 return noErr;
  2177.             default:
  2178.                 SysBeep(0);
  2179.                 return noErr;
  2180.         }
  2181.     }
  2182.     
  2183.     if (theChar == pageUpKey) {
  2184.         Scroll(wind, inPageUp);
  2185.         return noErr;
  2186.     }
  2187.     if (theChar == pageDownKey) {
  2188.         Scroll(wind, inPageDown);
  2189.         return noErr;
  2190.     }
  2191.     if (theChar == homeKey) {
  2192.         Scroll(wind, kScrollToHome);
  2193.         return noErr;
  2194.     }
  2195.     if (theChar == endKey) {
  2196.         Scroll(wind, kScrollToEnd);
  2197.         return noErr;
  2198.     }
  2199.     if (theChar == returnKey || theChar == enterKey) {
  2200.         return OpenSelectedCells(wind, (modifiers & optionKey) != 0);
  2201.     }
  2202.     if (isUpOrDownArrow) {
  2203.         ListArrowKey(theChar, modifiers, theList, &gPrevEvent, &scrollIntoView);
  2204.         HandleUpdate(wind);
  2205.         MyLScrollCellIntoView(scrollIntoView, theList);
  2206.         return noErr;
  2207.     }
  2208.     if (theChar == deleteKey) {
  2209.         if ((**info).groupKind == kUserGroup) return UnsubscribeSelected(wind);
  2210.     }
  2211.     
  2212.     if (gPrefs.keyboardShortcuts) {
  2213.     
  2214.         theChar = tolower(theChar);
  2215.         
  2216.         if (theChar == ' ' || theChar == 'n' || theChar == 'i') {
  2217.             return DoNextArticle(wind);
  2218.         }
  2219.         
  2220.         if (theChar == 't') {
  2221.             return DoNextThread(wind);
  2222.         }
  2223.         
  2224.         if (theChar == 'g' || theChar == 'j') {
  2225.             return DoNextGroup(wind);
  2226.         }
  2227.         
  2228.         if (theChar == 'w') {
  2229.             return DoClose(wind);
  2230.         }
  2231.         
  2232.         if (theChar == 'u') {
  2233.             return DoMarkCommand(wind, false);
  2234.         }
  2235.         
  2236.         if (theChar == 'm') {
  2237.             return DoMarkCommand(wind, true);
  2238.         }
  2239.         
  2240.         if (theChar == 'a') {
  2241.             DoSelectAll(wind);
  2242.             return noErr;
  2243.         }
  2244.         
  2245.     }
  2246.     
  2247.     SysBeep(0);
  2248.     
  2249.     return noErr;
  2250. }
  2251.  
  2252.  
  2253.  
  2254. /*----------------------------------------------------------------------------
  2255.     Grow 
  2256.     
  2257.     Handle a mouse down event in the grow box of a group window.
  2258.     
  2259.     Entry:    wind = pointer to group window.
  2260.             where = location of mouse down event, in global coordinates.
  2261.             
  2262.     Exit:    function result = error code.
  2263. ----------------------------------------------------------------------------*/
  2264.  
  2265. static OSErr Grow (WindowPtr wind, Point where)
  2266. {
  2267.     Rect sizeRect;
  2268.     long size;
  2269.     short width, height;
  2270.     TWindow **info;
  2271.     
  2272.     SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), 0x7fff, 0x7fff);
  2273.     size = GrowWindow(wind, where, &sizeRect);
  2274.     
  2275.     if (size  != 0) {
  2276.         width = LoWord(size);
  2277.         height = HiWord(size);
  2278.         FixHeight(wind, &height);
  2279.         SizeWindow(wind, width, height, false);
  2280.         ResizeContents(wind);
  2281.         info = (TWindow**)GetWRefCon(wind);
  2282.         (**info).windPosValid = true;
  2283.         (**info).movedSinceLastSave = true;
  2284.     }
  2285.     
  2286.     return noErr;
  2287. }
  2288.  
  2289.  
  2290.  
  2291. /*----------------------------------------------------------------------------
  2292.     Zoom
  2293.     
  2294.     Zoom a group window.
  2295.     
  2296.     Entry:    wind = pointer to group window.
  2297.             zoomDir = zoom direction = inZoomIn or inZoomOut.
  2298.             
  2299.     Exit:    function result = error code.
  2300. ----------------------------------------------------------------------------*/
  2301.  
  2302. static OSErr Zoom (WindowPtr wind, short zoomDir)
  2303. {
  2304.     TWindow **info;
  2305.     ListHandle theList;
  2306.     TGroup **groupArray;
  2307.     short width, height, lineHeight, minHeight, panelHeight;
  2308.     short groupNameWidth, maxGroupNameWidth, index, cellDataLen, numCells;
  2309.     Cell theCell;
  2310.     char *groupName;
  2311.     Rect zoomRect;    
  2312.     WStateData **wState;
  2313.     long longHeight;
  2314.     char state1, state2;
  2315.     
  2316.     info = (TWindow**)GetWRefCon(wind);
  2317.  
  2318.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  2319.     if (zoomDir == inZoomOut) {
  2320.         theList = (**info).theList;
  2321.         panelHeight = (**info).panelHeight;
  2322.         groupArray = (**info).groupArray;
  2323.         numCells = (**theList).dataBounds.bottom;
  2324.         lineHeight = (**theList).cellSize.v;
  2325.         
  2326.         if ((**info).groupKind == kFullGroup && gPrefs.maxGroupNameWidth != 0) {
  2327.             maxGroupNameWidth = gPrefs.maxGroupNameWidth;
  2328.         } else {
  2329.             maxGroupNameWidth = 0;
  2330.             theCell.h = 0;
  2331.             state1 = MyHGetState(groupArray);
  2332.             state2 = MyHGetState(gGroupNames);
  2333.             MyHLock(groupArray);
  2334.             MyHLock(gGroupNames);
  2335.             for (theCell.v=0; theCell.v < numCells; theCell.v++) {
  2336.                 cellDataLen = 2;
  2337.                 LGetCell(&index, &cellDataLen, theCell, theList);
  2338.                 groupName = *gGroupNames + (*groupArray)[index].nameOffset;
  2339.                 groupNameWidth = TextWidth(groupName, 0, strlen(groupName));
  2340.                 if (groupNameWidth > maxGroupNameWidth) maxGroupNameWidth = groupNameWidth;
  2341.             }
  2342.             MyHSetState(groupArray, state1);
  2343.             MyHSetState(gGroupNames, state2);
  2344.             if ((**info).groupKind == kFullGroup) gPrefs.maxGroupNameWidth = maxGroupNameWidth;
  2345.         }
  2346.         width = maxGroupNameWidth + (**info).groupNameHCoord + 24;
  2347.         if (width < kMinWindowWidth) width = kMinWindowWidth;
  2348.         
  2349.         longHeight = (long)((**theList).dataBounds.bottom -
  2350.             (**theList).dataBounds.top) * (long)lineHeight;
  2351.         longHeight += panelHeight + 15;
  2352.         if (longHeight > 0x7fff) {
  2353.             height = 0x7fff;
  2354.         } else {
  2355.             height = longHeight;
  2356.             minHeight = MinHeight(wind);
  2357.             if (height < minHeight) height = minHeight;
  2358.         }
  2359.  
  2360.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  2361.         height = zoomRect.bottom - zoomRect.top;
  2362.         FixHeight(wind, &height);
  2363.         zoomRect.bottom = zoomRect.top + height;
  2364.         (**wState).stdState = zoomRect;
  2365.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  2366.     }
  2367.     
  2368.     EraseRect(&wind->portRect);
  2369.     ZoomWindow(wind, zoomDir, false);
  2370.     ResizeContents(wind);
  2371.     (**info).windPosValid = true;
  2372.     (**info).movedSinceLastSave = true;
  2373.     return noErr;
  2374. }
  2375.  
  2376.  
  2377.  
  2378. /*----------------------------------------------------------------------------
  2379.     Command 
  2380.     
  2381.     Handle a command for a group window.
  2382.             
  2383.     Entry:    wind = pointer to group window.
  2384.             menu = the menu.
  2385.             item = the item.
  2386.             modifiers = modifiers field from event record.
  2387.     
  2388.     Exit:    function result = error code.
  2389. ----------------------------------------------------------------------------*/
  2390.  
  2391. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  2392. {
  2393.     OSErr err = noErr;
  2394.  
  2395.     switch (menu) {
  2396.                 
  2397.         case kFileMenu:
  2398.         
  2399.             switch (item) {
  2400.                 case kSaveItem:
  2401.                     err = DoSave(wind);
  2402.                     break;
  2403.                 case kSaveAsItem:
  2404.                     err = DoSaveAs(wind);
  2405.                     break;
  2406.             }
  2407.             break;
  2408.             
  2409.         case kEditMenu:
  2410.  
  2411.             switch (item) {
  2412.                 case kCutItem:
  2413.                     err = DoCut(wind);
  2414.                     break;
  2415.                 case kCopyItem:
  2416.                     err = DoCopy(wind);
  2417.                     break;
  2418.                 case kPasteItem:
  2419.                     err = DoPaste(wind);
  2420.                     break;
  2421.                 case kClearItem:
  2422.                     err = DoClear(wind);
  2423.                     break;
  2424.                 case kSelectAllItem:
  2425.                     DoSelectAll(wind);
  2426.                     break;
  2427.                 case kDeselectAllItem:
  2428.                     DoDeselectAll(wind);
  2429.                     break;
  2430.                 case kFindItem:
  2431.                     err = DoFind(wind);
  2432.                     break;
  2433.                 case kFindAgainItem:
  2434.                     err = DoFindAgain(wind);
  2435.                     break;
  2436.             }
  2437.             break;
  2438.  
  2439.         case kNewsMenu:
  2440.         
  2441.             switch (item) {
  2442.                 case kNextArticleItem:
  2443.                     err = DoNextArticle(wind);
  2444.                     break;
  2445.                 case kNextThreadItem:
  2446.                     err = DoNextThread(wind);
  2447.                     break;
  2448.                 case kNextGroupItem:
  2449.                     err = DoNextGroup(wind);
  2450.                     break;
  2451.                 case kMarkReadItem:
  2452.                     err = DoMarkCommand(wind, true);
  2453.                     break;
  2454.                 case kMarkUnreadItem:
  2455.                     err = DoMarkCommand(wind, false);
  2456.                     break;
  2457.                 case kCheckForNewArticlesItem:
  2458.                     err = DoCheckNewArticles(wind);
  2459.                     break;
  2460.             }
  2461.             break;
  2462.  
  2463.         case kSpecialMenu:
  2464.             switch (item) {
  2465.                 case kSendGroupListToHostItem:
  2466.                     err = DoSendGroupListToHost(wind, false);
  2467.                     break;
  2468.                 case kSearchItem:
  2469.                     err = DoSearch(wind);
  2470.                     break;
  2471.                 case kSubscribeItem:
  2472.                     err = DoSubscribe(wind);
  2473.                     break;
  2474.                 case kUnsubscribeItem:
  2475.                     err = DoUnsubscribe(wind);
  2476.                     break;
  2477.             }
  2478.             break;
  2479.      }
  2480.      
  2481.      return err;
  2482. }
  2483.  
  2484.  
  2485.  
  2486. /*----------------------------------------------------------------------------
  2487.     Close 
  2488.     
  2489.     Close a group window.
  2490.             
  2491.     Entry:    wind = pointer to group window.
  2492.     
  2493.     Exit:    function result = error code.
  2494. ----------------------------------------------------------------------------*/
  2495.  
  2496. static OSErr Close (WindowPtr wind)
  2497. {
  2498.     TWindow **info;
  2499.     TGroupWindowKind groupKind;
  2500.     OSErr err = noErr;
  2501.  
  2502.     info = (TWindow**)GetWRefCon(wind);
  2503.     groupKind = (**info).groupKind;
  2504.     
  2505.     if (groupKind == kFullGroup && gFullGroupWindow != nil) 
  2506.         return DoShowHideFullGroupList();
  2507.     
  2508.     if (groupKind == kUserGroup) {
  2509.         if ((**info).changed && (**info).autoFetched && gPrefs.autoFetchNewsrc) {
  2510.             err = DoSendGroupListToHost(wind, true);
  2511.             if (err != noErr) return err;
  2512.         }
  2513.         if ((**info).changed) {
  2514.             err = CheckForSave(wind);
  2515.             if (err != noErr) return err;
  2516.         }
  2517.     }
  2518.     
  2519.     err = SaveWindPosToFile(wind);
  2520.     if (err != noErr) return err;
  2521.     
  2522.     while ((**info).childList != nil) {
  2523.         err = DoClose((**(**info).childList).childWindow);
  2524.         if (err != noErr) return err;
  2525.     }
  2526.     
  2527.     if ((**info).autoFetched)
  2528.         SaveWindPos(wind, &gPrefs.autoFetchWindPos);
  2529.     
  2530.     DisposeGroupArray((**info).groupArray, (**info).numGroups);
  2531.     
  2532.     LDispose((**info).theList);
  2533.     MyDisposeHandle((**info).unsubscribed);
  2534.     MyDisposeHandle((**info).alias);
  2535.     MyDisposeHandle(info);
  2536.     
  2537.     if (groupKind == kUserGroup && gHaveDragMgr) {
  2538.         RemoveTrackingHandler(gHandleTrackingUPP, wind);
  2539.         RemoveReceiveHandler(gHandleReceiveUPP, wind);
  2540.     }
  2541.  
  2542.     MyDisposeWindow(wind);
  2543.     return noErr;
  2544. }
  2545.  
  2546.  
  2547.  
  2548. /*----------------------------------------------------------------------------
  2549.     Idle 
  2550.     
  2551.     Handle idle time tasks for a group window.
  2552.             
  2553.     Entry:    wind = pointer to group window.
  2554.     
  2555.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  2556. ----------------------------------------------------------------------------*/
  2557.  
  2558. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  2559. {
  2560.     TWindow **info, **theWindInfo;
  2561.     WindowPtr theWind;
  2562.     TGroupWindowKind groupKind;
  2563.     ListHandle theList;
  2564.     unsigned long fileEnabled, editEnabled, newsEnabled, specialEnabled, windEnabled;
  2565.     short numOpenUserGroupWindows;
  2566.     long scrapOffset;
  2567.     
  2568.     info = (TWindow**)GetWRefCon(wind);
  2569.     groupKind = (**info).groupKind;
  2570.     theList = (**info).theList;
  2571.     
  2572.     SetCursor(&qd.arrow);
  2573.     
  2574.     switch (groupKind) {
  2575.         case kFullGroup:
  2576.             fileEnabled = kGroupFileEnabled;
  2577.             editEnabled = kGroupEditEnabled;
  2578.             newsEnabled = kGroupNewsEnabled;
  2579.             specialEnabled = kGroupSpecialEnabled;
  2580.             windEnabled = kGroupWindEnabled;
  2581.             break;
  2582.         case kNewGroup:
  2583.             fileEnabled = kNewGroupFileEnabled;
  2584.             editEnabled = kNewGroupEditEnabled;
  2585.             newsEnabled = kNewGroupNewsEnabled;
  2586.             specialEnabled = kNewGroupSpecialEnabled;
  2587.             windEnabled = kNewGroupWindEnabled;
  2588.             break;
  2589.         case kUserGroup:
  2590.             fileEnabled = kUserGroupFileEnabled;
  2591.             if ((**info).alias != nil && !(**info).changed)
  2592.                 fileEnabled &= ~kSaveMask;
  2593.             editEnabled = kUserGroupEditEnabled;
  2594.             newsEnabled = kUserGroupNewsEnabled;
  2595.             specialEnabled = kUserGroupSpecialEnabled;
  2596.             windEnabled = kUserGroupWindEnabled;
  2597.             break;
  2598.     }
  2599.     if (!ListHasSelectedCell(theList)) {
  2600.         editEnabled &= ~(kCutMask | kCopyMask | kClearMask | kDeselectAllMask);
  2601.         newsEnabled &= ~(kMarkReadMask | kMarkUnreadMask);
  2602.         specialEnabled &= ~(kSearchMask | kSubscribeMask | kUnsubscribeMask);
  2603.     }
  2604.     if (GetScrap(nil, kNewsWatcherSignature, &scrapOffset) <= 0) 
  2605.         editEnabled &= ~kPasteMask;
  2606.     if ((**theList).dataBounds.bottom == 0) {
  2607.         editEnabled &= ~kSelectAllMask;
  2608.         newsEnabled &= ~kCheckForNewArticlesMask;
  2609.     }
  2610.     if ((specialEnabled & kSubscribeMask) != 0) {
  2611.         numOpenUserGroupWindows = 0;
  2612.         for (theWind = FrontWindow(); theWind != nil; 
  2613.             theWind = (WindowPtr)((WindowPeek)theWind)->nextWindow)
  2614.         {
  2615.             if (GetMyWindowKind(theWind) == kGroup) {
  2616.                 theWindInfo = (TWindow**)GetWRefCon(theWind);
  2617.                 if ((**theWindInfo).groupKind == kUserGroup) {
  2618.                     numOpenUserGroupWindows++;
  2619.                     if (numOpenUserGroupWindows > 1) {
  2620.                         specialEnabled &= ~kSubscribeMask;
  2621.                         break;
  2622.                     }
  2623.                 }
  2624.             }
  2625.         }
  2626.     }
  2627.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  2628.     SetMenusTo(kAppleAllEnabled, fileEnabled, editEnabled, newsEnabled,
  2629.         specialEnabled, windEnabled);
  2630. }
  2631.  
  2632.  
  2633.  
  2634. /*----------------------------------------------------------------------------
  2635.     Help 
  2636.     
  2637.     Handle help balloons for a group window.
  2638.             
  2639.     Entry:    wind = pointer to group window.
  2640.             where = current mouse location in local coordinates.
  2641. ----------------------------------------------------------------------------*/
  2642.  
  2643. static void Help (WindowPtr wind, Point where)
  2644. {
  2645.     TWindow **info;
  2646.     TGroupWindowKind groupKind;
  2647.     short index, groupNameHCoord;
  2648.     Rect r;
  2649.  
  2650.     if (DoSizeBoxAndVerticalScrollBarBalloons(wind, where)) return;
  2651.     info = (TWindow**)GetWRefCon(wind);
  2652.     groupKind = (**info).groupKind;
  2653.     r = wind->portRect;
  2654.     r.right -= 15;
  2655.     r.bottom -= 15;
  2656.     r.top = (**info).panelHeight;
  2657.     if (!PtInRect(where, &r)) return;
  2658.     switch (groupKind) {
  2659.         case kFullGroup:
  2660.             index = 10;
  2661.             break;
  2662.         case kNewGroup:
  2663.             index = 11;
  2664.             break;
  2665.         case kUserGroup:
  2666.             groupNameHCoord = (**info).groupNameHCoord;
  2667.             if (where.h < groupNameHCoord) {
  2668.                 index = 13;
  2669.                 r.right = groupNameHCoord;
  2670.             } else {
  2671.                 index = 12;
  2672.                 r.left = groupNameHCoord;
  2673.             }
  2674.             break;
  2675.     }
  2676.     ShowHelpBalloon(where, &r, index);
  2677. }
  2678.  
  2679.  
  2680.  
  2681. /*----------------------------------------------------------------------------
  2682.     InitGroupDispatchTable 
  2683.     
  2684.     Initialize the dispatch table for group windows.
  2685. ----------------------------------------------------------------------------*/
  2686.  
  2687. void InitGroupDispatchTable (void)
  2688. {
  2689.     TDispatch *d;
  2690.     
  2691.     d = &gDispatch[kGroup];
  2692.     
  2693.     d->activate = Activate;
  2694.     d->update = Update;
  2695.     d->mouse = Mouse;
  2696.     d->draggable = Draggable;
  2697.     d->key = Key;
  2698.     d->grow = Grow;
  2699.     d->zoom = Zoom;
  2700.     d->command = Command;
  2701.     d->close = Close;
  2702.     d->idle = Idle;
  2703.     d->help = Help;
  2704.     
  2705.     gGroupListClickLoopUPP = NewListClickLoopProc(GroupListClickLoop);
  2706.     gOldListClickLoopUPP = NewListClickLoopProc(OldListClickLoop);
  2707.     gHandleTrackingUPP = NewDragTrackingHandlerProc(HandleTracking);
  2708.     gHandleReceiveUPP = NewDragReceiveHandlerProc(HandleReceive);
  2709. }
  2710.